# 3.5 模型初始化 在深度学习模型的训练中,权重的初始值极为重要。一个好的初始值,会使模型收敛速度提高,使模型准确率更精确。一般情况下,我们不使用全0初始值训练网络。为了利于训练和减少收敛时间,我们需要对模型进行合理的初始化。PyTorch也在`torch.nn.init`中为我们提供了常用的初始化方法。 通过本章学习,你将学习到以下内容: - 常见的初始化函数 - 初始化函数的使用 ## torch.nn.init内容 通过访问torch.nn.init的官方文档[链接](https://pytorch.org/docs/stable/nn.init.html) ,我们发现`torch.nn.init`提供了以下初始化方法: 1 . `torch.nn.init.uniform_`(tensor, a=0.0, b=1.0) 2 . `torch.nn.init.normal_`(tensor, mean=0.0, std=1.0) 3 . `torch.nn.init.constant_`(tensor, val) 4 . `torch.nn.init.ones_`(tensor) 5 . `torch.nn.init.zeros_`(tensor) 6 . `torch.nn.init.eye_`(tensor) 7 . `torch.nn.init.dirac_`(tensor, groups=1) 8 . `torch.nn.init.xavier_uniform_`(tensor, gain=1.0) 9 . `torch.nn.init.xavier_normal_`(tensor, gain=1.0) 10 . `torch.nn.init.kaiming_uniform_`(tensor, a=0, mode='fan__in', nonlinearity='leaky_relu') 11 . `torch.nn.init.kaiming_normal_`(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu') 12 . `torch.nn.init.orthogonal_`(tensor, gain=1) 13 . `torch.nn.init.sparse_`(tensor, sparsity, std=0.01) 14 . `torch.nn.init.calculate_gain`(nonlinearity, param=None) 关于计算增益如下表: |nonlinearity|gain| | ---- | ---- | |Linear/Identity|1| |Conv{1,2,3}D|1| |Sigmod|1| |Tanh|5/3| |ReLU|sqrt(2)| |Leaky Relu|sqrt(2/1+neg_slop^2)| 我们可以发现这些函数除了`calculate_gain`,所有函数的后缀都带有下划线,意味着这些函数将会直接原地更改输入张量的值。 ## torch.nn.init使用 我们通常会根据实际模型来使用`torch.nn.init`进行初始化,通常使用`isinstance()`来进行判断模块(回顾3.4模型构建)属于什么类型。 ```python import torch import torch.nn as nn conv = nn.Conv2d(1,3,3) linear = nn.Linear(10,1) isinstance(conv,nn.Conv2d) # 判断conv是否是nn.Conv2d类型 isinstance(linear,nn.Conv2d) # 判断linear是否是nn.Conv2d类型 ``` ```python True False ``` 对于不同的类型层,我们就可以设置不同的权值初始化的方法。 ```python # 查看随机初始化的conv参数 conv.weight.data # 查看linear的参数 linear.weight.data ``` ```python tensor([[[[ 0.1174, 0.1071, 0.2977], [-0.2634, -0.0583, -0.2465], [ 0.1726, -0.0452, -0.2354]]], [[[ 0.1382, 0.1853, -0.1515], [ 0.0561, 0.2798, -0.2488], [-0.1288, 0.0031, 0.2826]]], [[[ 0.2655, 0.2566, -0.1276], [ 0.1905, -0.1308, 0.2933], [ 0.0557, -0.1880, 0.0669]]]]) tensor([[-0.0089, 0.1186, 0.1213, -0.2569, 0.1381, 0.3125, 0.1118, -0.0063, -0.2330, 0.1956]]) ``` ```python # 对conv进行kaiming初始化 torch.nn.init.kaiming_normal_(conv.weight.data) conv.weight.data # 对linear进行常数初始化 torch.nn.init.constant_(linear.weight.data,0.3) linear.weight.data ``` ```python tensor([[[[ 0.3249, -0.0500, 0.6703], [-0.3561, 0.0946, 0.4380], [-0.9426, 0.9116, 0.4374]]], [[[ 0.6727, 0.9885, 0.1635], [ 0.7218, -1.2841, -0.2970], [-0.9128, -0.1134, -0.3846]]], [[[ 0.2018, 0.4668, -0.0937], [-0.2701, -0.3073, 0.6686], [-0.3269, -0.0094, 0.3246]]]]) tensor([[0.3000, 0.3000, 0.3000, 0.3000, 0.3000, 0.3000, 0.3000, 0.3000, 0.3000,0.3000]]) ``` ## 初始化函数的封装 人们常常将各种初始化方法定义为一个`initialize_weights()`的函数并在模型初始后进行使用。 ```python def initialize_weights(model): for m in model.modules(): # 判断是否属于Conv2d if isinstance(m, nn.Conv2d): torch.nn.init.zeros_(m.weight.data) # 判断是否有偏置 if m.bias is not None: torch.nn.init.constant_(m.bias.data,0.3) elif isinstance(m, nn.Linear): torch.nn.init.normal_(m.weight.data, 0.1) if m.bias is not None: torch.nn.init.zeros_(m.bias.data) elif isinstance(m, nn.BatchNorm2d): m.weight.data.fill_(1) m.bias.data.zeros_() ``` 这段代码流程是遍历当前模型的每一层,然后判断各层属于什么类型,然后根据不同类型层,设定不同的权值初始化方法。我们可以通过下面的例程进行一个简短的演示: ```python # 模型的定义 class MLP(nn.Module): # 声明带有模型参数的层,这里声明了两个全连接层 def __init__(self, **kwargs): # 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数 super(MLP, self).__init__(**kwargs) self.hidden = nn.Conv2d(1,1,3) self.act = nn.ReLU() self.output = nn.Linear(10,1) # 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出 def forward(self, x): o = self.act(self.hidden(x)) return self.output(o) mlp = MLP() print(mlp.hidden.weight.data) print("-------初始化-------") mlp.apply(initialize_weights) # 或者initialize_weights(mlp) print(mlp.hidden.weight.data) ``` ```python tensor([[[[ 0.3069, -0.1865, 0.0182], [ 0.2475, 0.3330, 0.1352], [-0.0247, -0.0786, 0.1278]]]]) "-------初始化-------" tensor([[[[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]]]) ``` **注意:** 我们在初始化时,最好不要将模型的参数初始化为0,因为这样会导致梯度消失,从而影响模型的训练效果。因此,我们在初始化时,可以使用其他初始化方法或者将模型初始化为一个很小的值,如0.01,0.1等。